package com.googlecode.classgrep.visitor.invoker;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import jw.asmsupport.clazz.AClass;
import jw.asmsupport.clazz.AClassFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.objectweb.asm.Type;

import com.googlecode.classgrep.entity.invoker.LocalVariableSignature;
import com.googlecode.classgrep.info.InvokeInfo;
import com.googlecode.classgrep.info.InvokeInfo.FunctionInfo;
import com.googlecode.classgrep.utils.CommonUtils;

public class MethodInvokeGrepVisitor extends StackLocalMethodVisitor {
	
	
    private static final Log LOG = LogFactory.getLog(MethodInvokeGrepVisitor.class);

	private List<FunctionInfo> calledFuns;

	private String[] calledFunNames;
	
	private String[] calledFunDescs;
	
	private Class<?>[] calledFunOwners;
	
	private Type     callingFunOwner;
	
	private FunctionInfo   callingFun;
	
	private Map<FunctionInfo, List<InvokeInfo>> invokeInfos;
	
	private ClassLoader classLoader;
	
	public MethodInvokeGrepVisitor(
			int callingFunModifier, 
			String callingFunName,
			String callingFunDesc, 
			Type callingFunOwner, 
			List<LocalVariableSignature> callingFunLocVarSigns, 
			List<FunctionInfo> calledFuns, 
			Map<FunctionInfo, List<InvokeInfo>> invokeInfoMap,
			ClassLoader classLoader) {
		
		super(callingFunDesc, callingFunModifier, callingFunOwner, callingFunLocVarSigns);
		
		this.calledFuns = calledFuns;
		this.classLoader = classLoader;
		
		this.calledFunNames = new String[calledFuns.size()];//calledFun.getName();
		this.calledFunDescs = new String[calledFuns.size()];//Type.getMethodDescriptor(calledFun);
		this.calledFunOwners = new Class<?>[calledFuns.size()];//calledFun.getDeclaringClass();
		
		for(int i=0, len=calledFuns.size(); i<len; i++){
			FunctionInfo calledFun = calledFuns.get(i);
			this.calledFunNames[i] = calledFun.getName();
			this.calledFunDescs[i] = calledFun.getDescription();
			this.calledFunOwners[i] = calledFun.getDeclaringClass();
		}
		
		
		this.callingFunOwner = callingFunOwner;
		this.invokeInfos = invokeInfoMap;
		
		//reflect to get calling function
		try {
			
			try {
				if (callingFunName.equals("<init>")) {
					
					callingFun = InvokeInfo.buildConstructorFunctionInfo(CommonUtils.reflect2Constructor(callingFunOwner.getDescriptor()
							, callingFunDesc, classLoader));
				
				}else if(callingFunName.equals("<clinit>")){
					Class<?> callingFunOwnerCls = CommonUtils.forName(callingFunOwner.getDescriptor(), true, classLoader);
					callingFun = InvokeInfo.buildStaticFunctionInfo(callingFunOwnerCls);
				
				}else{
					
					callingFun = InvokeInfo.buildCommonFunctionInfo(CommonUtils.reflect2Method(callingFunOwner.getDescriptor(),
							callingFunName, callingFunDesc, classLoader));
				
				}
			} catch (Exception e) {
				LOG.error("cannot get method : " + callingFunName + " cause by : " + e.getMessage());
			}
			
		} catch (Exception e) {
			LOG.error("cannot load class : " + callingFunOwner + " cause by : " + e.getMessage());
		}
		
	}

	@Override
	public void visitMethodInsn(int opcode, String owner, String name, String desc) {
		for(int idx=0, len=calledFuns.size(); idx<len; idx++){
			String calledFunDesc = calledFunDescs[idx];
			String calledFunName = calledFunNames[idx];
			Class<?> calledFunOwner = calledFunOwners[idx];
			FunctionInfo calledFun = calledFuns.get(idx);
			
			if(desc.equals(calledFunDesc) && name.equals(calledFunName)){
				try{
					AClass ownerClass = AClassFactory.getProductClass( CommonUtils.forName(owner.replace("/", "."), true, classLoader));
					AClass byInvokedMethodOwnerAClass = AClassFactory.getProductClass(calledFunOwner);
					if(ownerClass.isChildOrEqual(byInvokedMethodOwnerAClass)){
						
						int argumentsSize = calledFun.getParameterTypes().length;
						List<Type> allTypeInStack = new ArrayList<Type>();
						for(int i=0; i<argumentsSize;i++){
							allTypeInStack.add(stack.get(stack.size() - 1 - i));
						}
						
						Class<?>[] argumentActuallyTypes = new Class<?>[argumentsSize];
						for(int i=argumentsSize; i>0;i--){
							Type typeInStack = allTypeInStack.get(i-1);
							Class<?> argCls = null; 
							int sort = typeInStack.getSort();
							switch(sort){
							case 1 :
						        argCls = boolean.class;
						        break;
							case 2 :
								argCls = char.class;
						        break;
							case 3 :
								argCls = byte.class;
						        break;
							case 4 :
								argCls = short.class;
						        break;
							case 5 :
								argCls = int.class;
						        break;
							case 6 :
								argCls = float.class;
						        break;
							case 7 :
								argCls = long.class;
						        break;
							case 8 :
								argCls = double.class;
						        break;
							case 9 :
								argCls = CommonUtils.forName(typeInStack.getDescriptor(), true, classLoader);
						        break;
							default :
							    argCls = CommonUtils.forName(typeInStack.getClassName(), true, classLoader);
						        break;
							}
							argumentActuallyTypes[argumentsSize-i] = argCls;
						}
						
						InvokeInfo invokeInfo = new InvokeInfo(CommonUtils.forName(callingFunOwner.getClassName(), true, classLoader),
								callingFun, calledFun, argumentActuallyTypes, currentLineNumber);
						
						invokeInfos.get(calledFun).add(invokeInfo);
					}
				} catch (Throwable e) {
					LOG.warn("error when extart method invoke information : " + calledFun + " cause by : " + e.getMessage(), e);
			    }
			}
		}
		
		super.visitMethodInsn(opcode, owner, name, desc);
	}
	
	

	
}
